package com.hbpa.pan.server.modules.file.service.impl;

import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.hbpa.pan.core.constants.RPanConstants;
import com.hbpa.pan.core.exception.RPanBusinessException;
import com.hbpa.pan.core.utils.IdUtil;
import com.hbpa.pan.server.common.event.file.DeleteFileEvent;
import com.hbpa.pan.server.modules.file.constants.FileConstants;
import com.hbpa.pan.server.modules.file.context.CreateFolderContext;
import com.hbpa.pan.server.modules.file.context.DeleteFileContext;
import com.hbpa.pan.server.modules.file.context.QueryFilelistContext;
import com.hbpa.pan.server.modules.file.context.UpdateFilenameContext;
import com.hbpa.pan.server.modules.file.entity.RPanUserFile;
import com.hbpa.pan.server.modules.file.enums.DelFlagEnum;
import com.hbpa.pan.server.modules.file.enums.FolderFlagEnum;
import com.hbpa.pan.server.modules.file.service.IUserFileService;
import com.hbpa.pan.server.modules.file.mapper.RPanUserFileMapper;
import com.hbpa.pan.server.modules.file.vo.RPanUserFileVO;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.stereotype.Service;


import java.util.Date;
import java.util.List;
import java.util.Objects;
import java.util.Set;
import java.util.stream.Collectors;

/**
 *用户文件夹信息业务实现类
 */
@Service
public class UserFileServiceImpl extends ServiceImpl<RPanUserFileMapper, RPanUserFile>
    implements IUserFileService, ApplicationContextAware {

    /**
     * spring容器高级封装
     */

    private ApplicationContext applicationContext;

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        this.applicationContext = applicationContext;
    }

    /**
     * 创建文件夹信息
     * @param createFolderContext
     * @return
     */
    @Override
    public Long createFolder(CreateFolderContext createFolderContext) {
        Long fileId = saveUserFile(createFolderContext.getParentId(),
                createFolderContext.getFolderName(),
                FolderFlagEnum.YES,
                null,
                null,
                createFolderContext.getUserId(),
                null);
        return fileId;
    }

    /**
     * 获取用户的根文件夹信息
     *
     * @param userId
     * @return
     */
    @Override
    public RPanUserFile getUserRootFile(Long userId) {
        LambdaQueryWrapper<RPanUserFile> queryWrapper = new LambdaQueryWrapper();
        queryWrapper.eq(true,RPanUserFile::getUserId,userId)
                .eq(true,RPanUserFile::getParentId,FileConstants.TOP_PARENT_ID)
                .eq(true,RPanUserFile::getDelFlag,DelFlagEnum.NO.getCode())
                .eq(true,RPanUserFile::getFolderFlag,FolderFlagEnum.YES.getCode());
        return getOne(queryWrapper);
    }

    /**
     * 查询用户的文件列表
     *
     * @return
     */
    @Override
    public List<RPanUserFileVO> getFileList(QueryFilelistContext queryFilelistContext) {
        return baseMapper.selectFileList(queryFilelistContext);
    }

    /**
     * 更新文件名称
     * 1、校验更新文件名称的条件
     * 2、执行更新文件名称的操作
     *
     * @param context
     */
    @Override
    public void updateFilename(UpdateFilenameContext context) {
        checkUpdateFilenameCondition(context);
        doUpdateFilename(context);
    }

    /**
     * 批量删除文件业务实现
     * 1.校验删除的文件
     * 2.执行批量删除的操作
     * 3.发布批量删除文件的事件，给其他模块订阅使用
     *
     * @param context
     * @return
     */
    @Override
    public void deleteFile(DeleteFileContext context) {
        checkFileDeledition(context);
        doDeleteFile(context);
        afterFileDelete(context);
    }




    /************************************private************************************/

    /**
     * 对外发布文件删除的时间
     * @param context
     */
    private void afterFileDelete(DeleteFileContext context) {
        DeleteFileEvent deleteFileEvent = new DeleteFileEvent(this,context.getFileIdList());
        applicationContext.publishEvent(deleteFileEvent);
    }

    /**
     * 执行删除业务操作
     * @param context
     */
    private void doDeleteFile(DeleteFileContext context) {
        List<Long> fileIdList = context.getFileIdList();
        LambdaUpdateWrapper<RPanUserFile> updateWrapper = new LambdaUpdateWrapper<>();
        updateWrapper.in(true,RPanUserFile::getFileId,fileIdList);
        updateWrapper.set(true,RPanUserFile::getDelFlag,DelFlagEnum.NO.getCode());
        updateWrapper.set(true,RPanUserFile::getUpdateTime,new Date());;
        if (!update(updateWrapper)) {
            throw new RPanBusinessException("文件删除失败");
        }
    }

    /**
     * 删除文件之前的前置校验
     * 1.文件id合法校验
     * 2.用户是否拥有删除该文件的权限
     *
     * @param context
     */
    private void checkFileDeledition(DeleteFileContext context) {
        List<Long> fileIdList = context.getFileIdList();
        //根据用户传递的id列表查询用户文件信息
        List<RPanUserFile> rPanUserFiles = listByIds(fileIdList);
        //判断数量
        if (rPanUserFiles.size() != fileIdList.size()){
            throw new RPanBusinessException("存在不合法的文件记录(文件id记录不存在)");
        }
        //TODO 好像没有必要进行判断，后续再来处理吧！
        //判断每个文件id(利用set不保存重复数据的特性)
        Set<Long> fileIdSet = rPanUserFiles.stream().map(RPanUserFile::getFileId).collect(Collectors.toSet());
        int oldSize = fileIdSet.size();
        fileIdSet.addAll(fileIdList);
        int newSize = fileIdSet.size();
        if (oldSize!=newSize){
            throw new RPanBusinessException("存在不合法的文件记录(文件id不存在)");
        }

        Set<Long> userIdSet = rPanUserFiles.stream().map(RPanUserFile::getUserId).collect(Collectors.toSet());
        if (userIdSet.size()!=1){
            throw new RPanBusinessException("存在不合法的文件记录(用户权限错误)");
        }
        userIdSet.add(context.getUserId());
        //TODO 这里自定义业务处理，和video不同，如有错误，请及时回来修改
        if (userIdSet.size()!=1){
            throw new RPanBusinessException("存在不合法的文件记录(用户权限错误)");
        }
    }

    /**
     * 更新文件名称
     * @param context
     */
    private void doUpdateFilename(UpdateFilenameContext context) {
        RPanUserFile entity = context.getEntity();
        entity.setFilename(context.getNewFilename());
        entity.setUpdateUser(context.getUserId());
        entity.setUpdateTime(new Date());

        if (!updateById(entity)){
            throw new RPanBusinessException("更新文件名称失败");
        }
    }

    /**
     * 更新文件名称的条件校验
     * 1、文件ID是有效的
     * 2、用户有权限更新该文件的文件名称
     * 3、新旧文件名称不能一致
     * 4、不能使用当前文件夹下面的子文件的名称(同一文件夹下不能有重复的名称)
     *
     * @param context
     */
    private void checkUpdateFilenameCondition(UpdateFilenameContext context) {

        Long fileId = context.getFileId();
        RPanUserFile entity = getById(fileId);
        if (Objects.isNull(entity)){
            throw new RPanBusinessException("当前文件ID无效");
        }
        if (!Objects.equals(entity.getUserId(),context.getUserId())){
            throw new RPanBusinessException("当前用户没有修改该文件的权限");
        }
        if (entity.getFilename().equals(context.getNewFilename())){
            throw new RPanBusinessException("新文件夹名称不能与旧文件一致");
        }

        LambdaQueryWrapper<RPanUserFile> queryWrapper = new LambdaQueryWrapper();
        queryWrapper.eq(true,RPanUserFile::getParentId,entity.getParentId())
                .eq(true,RPanUserFile::getFilename,context.getNewFilename())
                .eq(true,RPanUserFile::getUserId,entity.getUserId());
        int count = this.count(queryWrapper);

        if (count > 0){
            throw new RPanBusinessException("该文件名称已被占用");
        }
        //将实体信息存入上下文对象，以便下游业务使用
        context.setEntity(entity);
    }

    /**
     * 保存用户文件的映射记录(在用户-文件映射表中添加数据)
     * @param parentId
     * @param filename
     * @param folderFlagEnum
     * @param fileType
     * @param realFileId
     * @param fileSizeDesc
     * @return
     */
    private Long saveUserFile(Long parentId, String filename, FolderFlagEnum folderFlagEnum,Integer fileType,Long realFileId,Long userId,String fileSizeDesc){
        //组装用户文件实体
        RPanUserFile entity = assembleRPanUserFile(parentId,filename,folderFlagEnum,fileType,realFileId,userId,fileSizeDesc);
        //处理用户名称重复
        handleDuplicateFilename(entity);

        if (!this.save(entity)){
            throw new RPanBusinessException("保存文件信息失败");
        }
        return entity.getFileId();
    }

    /**
     * 用户文件映射关系实体转化
     * 1.构建并填充实体信息
     * 2.处理文件命名一致的问题
     *
     * @param parentId
     * @param filename
     * @param folderFlagEnum
     * @param fileType
     * @param realFileId
     * @param userId
     * @param fileSizeDesc
     * @return
     */
    private RPanUserFile assembleRPanUserFile(Long parentId, String filename, FolderFlagEnum folderFlagEnum, Integer fileType, Long realFileId, Long userId, String fileSizeDesc) {
        RPanUserFile entity = new RPanUserFile();
        //文件-用户映射的记录id(雪花算法生成文件id)
        entity.setFileId(IdUtil.get());
        entity.setUserId(userId);
        entity.setParentId(parentId);
        //TODO 可能需要修改为文件id的解密id，或者是对应文件表的id
        entity.setRealFileId(realFileId);
        entity.setFilename(filename);
        entity.setFolderFlag(folderFlagEnum.getCode());
        entity.setFileType(fileType);
        entity.setFileSizeDesc(fileSizeDesc);
        entity.setDelFlag(DelFlagEnum.NO.getCode());
        Date now = new Date();
        entity.setCreateTime(now);
        entity.setUpdateTime(now);
        entity.setCreateUser(userId);
        entity.setUpdateUser(userId);
        return  entity;
    }

    /**
     * 处理用户重复名称
     * 如果同一文件夹下面有文件名称重复
     * 按照系统级规则重命名文件
     * TODO 文件名查重有bug，第二次创建会重复创建上次的文件名
     * @param entity
     */
    private void handleDuplicateFilename(RPanUserFile entity) {
        //文件或目录名
        String filename = entity.getFilename();
        //文件不包含后缀
        String newFilenameWithoutSuffix;
        //文件后缀
        String newFilenameSuffix;
        //文件中点的索引
        int newFilenamePointPosition = filename.lastIndexOf(RPanConstants.POINT_STR);
        //文件名后缀不存在
        if (newFilenamePointPosition == RPanConstants.MINUS_ONE_INT){
            //不包含后缀文件名 == filename
            newFilenameWithoutSuffix = filename;
            //后缀为空
            newFilenameSuffix = StringUtils.EMPTY;
        }else{
            //不包含后缀名 == 0-文件中点的索引
            newFilenameWithoutSuffix = filename.substring(RPanConstants.ZERO_INT,newFilenamePointPosition);
            //后缀 == 原文件名替换0到"."为空
            newFilenameSuffix = filename.replace(newFilenameWithoutSuffix,StringUtils.EMPTY);
        }

        int count = getDuplicateFilename(entity,newFilenameWithoutSuffix);
        //没有重复文件名，不做处理
        if (count == RPanConstants.ZERO_INT){
            return;
        }
        //若有文件重复，根据
        String newFilename = assembleNewFilename(newFilenameWithoutSuffix,count,newFilenameSuffix);
        entity.setFilename(newFilename);
    }

    /**
     * 查找同一父目录下的同名文件的数量
     * @param entity
     * @param newFilenameWithoutSuffix
     * @return
     */
    private int getDuplicateFilename(RPanUserFile entity, String newFilenameWithoutSuffix) {
        LambdaQueryWrapper<RPanUserFile> queryWrapper = new LambdaQueryWrapper();
        queryWrapper.eq(RPanUserFile::getParentId,entity.getParentId());
        queryWrapper.eq(RPanUserFile::getFolderFlag,entity.getFolderFlag());
        queryWrapper.eq(RPanUserFile::getDelFlag,entity.getDelFlag());
        queryWrapper.eq(RPanUserFile::getUserId,entity.getUserId());
        queryWrapper.likeLeft(RPanUserFile::getFilename,entity.getFilename());
        return this.count(queryWrapper);
    }

    /**
     * 组装新文件名称
     * 拼装规则参考操作系统重复文件名称的重命名规范
     *
     * @param newFilenameWithoutSuffix
     * @param count
     * @param newFilenameSuffix
     * @return
     */
    private String assembleNewFilename(String newFilenameWithoutSuffix, int count, String newFilenameSuffix) {
        String newFilename = new StringBuilder(newFilenameWithoutSuffix)
                .append(FileConstants.CN_LEFT_PARENTHESES_STR)
                .append(count)
                .append(FileConstants.CN_RIGHT_PARENTHESES_STR)
                .append(RPanConstants.POINT_STR)
                .append(newFilenameSuffix)
                .toString();
        return newFilename;
    }


}




